/**
 * Software License, Version 1.0
 * 
 * Copyright 2003 The Trustees of Indiana University.  All rights reserved.
 * 
 *
 *Redistribution and use in source and binary forms, with or without 
 *modification, are permitted provided that the following conditions are met:
 *
 *1) All redistributions of source code must retain the above copyright notice,
 * the list of authors in the original source code, this list of conditions and
 * the disclaimer listed in this license;
 *2) All redistributions in binary form must reproduce the above copyright 
 * notice, this list of conditions and the disclaimer listed in this license in
 * the documentation and/or other materials provided with the distribution;
 *3) Any documentation included with all redistributions must include the 
 * following acknowledgement:
 *
 *"This product includes software developed by the Community Grids Lab. For 
 * further information contact the Community Grids Lab at 
 * http://communitygrids.iu.edu/."
 *
 * Alternatively, this acknowledgement may appear in the software itself, and 
 * wherever such third-party acknowledgments normally appear.
 * 
 *4) The name Indiana University or Community Grids Lab or NaradaBrokering, 
 * shall not be used to endorse or promote products derived from this software 
 * without prior written permission from Indiana University.  For written 
 * permission, please contact the Advanced Research and Technology Institute 
 * ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202.
 *5) Products derived from this software may not be called NaradaBrokering, 
 * nor may Indiana University or Community Grids Lab or NaradaBrokering appear
 * in their name, without prior written permission of ARTI.
 * 
 *
 * Indiana University provides no reassurances that the source code provided 
 * does not infringe the patent or any other intellectual property rights of 
 * any other entity.  Indiana University disclaims any liability to any 
 * recipient for claims brought by any other entity based on infringement of 
 * intellectual property rights or otherwise.  
 *
 *LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO 
 *WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
 *NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF 
 *INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS. 
 *INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS", 
 *"VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.  
 *LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR 
 *ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION 
 *GENERATED USING SOFTWARE.
 */
package cgl.narada.service.time.ntptime;

import java.util.Calendar;

/**
 * This class encapsulates the header of a NtpDatagram. See rfc2030 for more
 * details.
 *
 *                            1                   2                   3
 *      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 *     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *     |LI | VN  |Mode |    Stratum    |     Poll      |   Precision   |
 *     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *     |                          Root Delay                           |
 *     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *     |                       Root Dispersion                         |
 *     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *     |                     Reference Identifier                      |
 *     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *
 * @author Hasan Bulut - hbulut@indiana.edu
 */
public class NtpHeader {
  //~ Static fields/initializers -----------------------------------------------

  /**
   * The default header data for a client datagram. Version=3, Mode=client.
   */

  // version 4: => 0x23
  public static final byte[] defaultHeaderData = {
                                                   (byte) 0x1B, 0, 0, 0, 0, 0, 0,
                                                   0, 0, 0, 0, 0, 0, 0, 0, 0
                                                 };

  /**
   * The default header for a client datagram. This is a wrapper around
   * defaultHeaderData
   */
  public static final NtpHeader defaultHeader = new NtpHeader(defaultHeaderData);

  //~ Instance fields ----------------------------------------------------------

  byte[] headerData = new byte[NtpOffset.offReferenceTimestamp -
                      NtpOffset.offHeader];

  //~ Constructors -------------------------------------------------------------

  /**
   * Construct a NtpHeader from a 16 byte array.
   */
  public NtpHeader(byte[] data) {
    this.headerData = data;
  }

  /**
   * Construct a NtpHeader from a byte array and offset
   */
  public NtpHeader(int offset, byte[] data) {
    headerData = new byte[NtpOffset.offReferenceTimestamp -
                 NtpOffset.offHeader];
    for (int i = NtpOffset.offHeader; i < NtpOffset.offReferenceTimestamp;
         i++) {
      headerData[i - NtpOffset.offHeader] = data[i];
    }
  }

  //~ Methods ------------------------------------------------------------------

  /**
   * Gets the 16 byte array constituting the header.
   * @return byte [] - header data
   */
  public byte[] getHeaderData() {
    return headerData;
  }

  /**
   * Gets leap indicator
   * @return int - leap indicator
   */
  public int getLeapIndicator() {
    // Isolate the two most significant bits
    return (int) (headerData[0] >> 6);
  }

  /**
   * Gets mode
   * @return int - mode
   */
  public int getMode() {
    return (int) (headerData[0] & 0x7);
  }

  /**
   * Gets poll interval
   * @return int - poll interval
   */
  public int getPollInterval() {
    return (int) Math.round(Math.pow(2, headerData[2]));
  }

  /**
   * Gets precision in milliseconds
   * @return double - precision
   */
  public double getPrecision() {
    return (1000 * Math.pow(2, headerData[3]));
  }

  /**
   * Gets ReferenceID with the given stratum and version number.
   * @param stratumVal stratum value of the server
   * @param versionVal version number of the packet
   * @return String - reference ID
   */
  public String getReferenceID(int stratumVal, int versionVal) {
    String val = "";
    switch (stratumVal) {
      case Stratum.UNSPECIFIED:
      case Stratum.PRIMARY_REFERENCE:
        val += (char) headerData[NtpOffset.offReferenceID + 0];
        val += (char) headerData[NtpOffset.offReferenceID + 1];
        val += (char) headerData[NtpOffset.offReferenceID + 2];
        val += (char) headerData[NtpOffset.offReferenceID + 3];
        break;
      case Stratum.SECONDARY_REFERENCE:
        switch (versionVal) {
          case 3: // Version 3, Reference ID is an IPv4 address
            String Address = mp(headerData[NtpOffset.offReferenceID + 0]) +
                             "." +
                             mp(headerData[NtpOffset.offReferenceID + 1]) +
                             "." +
                             mp(headerData[NtpOffset.offReferenceID + 2]) +
                             "." +
                             mp(headerData[NtpOffset.offReferenceID + 3]);
            try {
              val = Address;
            } catch (Exception ex) {
              ex.printStackTrace();
              val = "N/A";
            }
            break;
          case 4: // Version 4, Reference ID is the timestamp of last update
            NtpTimestamp ntpTS = new NtpTimestamp();
            ntpTS.setTimestamp(NtpOffset.offReferenceID, headerData);
            Calendar time = ComputeDate(ntpTS.getTimeInMilliSeconds());

            // Take care of the time zone
            // time.add();
            val = time.toString();
            break;
          default:
            val = "N/A";
            break;
        }
        break;
    }
    return val;
  }

  /**
   * Gets ReferenceID using the stratum and version number of the packet.
   * @return String - reference ID
   */
  public String getReferenceID() {
    int stratumVal = this.getStratum();
    int versionVal = this.getVersionNumber();
    return this.getReferenceID(stratumVal, versionVal);
  }

  /**
   * Gets root delay in milliseconds.
   * @return double - root delay
   */
  public double getRootDelay() {
    int temp = 0;
    temp = 256 * (256 * (256 * headerData[4] + headerData[5]) + headerData[6]) +
           headerData[7];
    return 1000 * (((double) temp) / 0x10000);
  }

  /**
   * Gets root dispersion.
   * @return double - root dispersion
   */
  public double getRootDispersion() {
    int temp = 0;
    temp = 256 * (256 * (256 * headerData[8] + headerData[9]) + headerData[10]) +
           headerData[11];
    return 1000 * (((double) temp) / 0x10000);
  }

  /**
   * Gets stratum
   * @return int - stratum
   */
  public int getStratum() {
    return (int) headerData[1];
  }

  /**
   * Gets version number
   * @return int - version number
   */
  public int getVersionNumber() {
    return (int) ((headerData[0] & 0x38) >> 3);
  }

  /**
   * Computes date, given the number of milliseconds since January 1, 1900
   * @param milliseconds
   * @return Calendar - time as Calendar
   */
  private Calendar ComputeDate(long milliseconds) {
    Calendar time = Calendar.getInstance();
    time.setTimeInMillis(milliseconds);
    return time;
  }

  /**
   * If int value of byte is negative, it is returned after added with 256,
   * otherwise the int value of the byte is returned.
   * @param b byte
   * @return  int
   */
  private int mp(byte b) {
    int bb = b;
    return (bb < 0) ? 256 + bb : bb;
  }
}
